package net.cyndeline.rldungeon.grammar.production

import scalax.collection.GraphEdge.UnDiEdge
import scalax.collection.immutable.Graph
import net.cyndeline.rlgraph.isomorphism.{IsomorphicMapping, NegativeCondition, ElementEquivalence}
import scala.util.Random
import net.cyndeline.rldungeon.util.{RandomCollection, ProbabilityCollection}
import net.cyndeline.rldungeon.grammar.util.{MorphismFactory, Morphism}
import net.cyndeline.rldungeon.grammar.ComponentProduction
import scala.reflect.runtime.universe._
import scala.reflect.ClassTag
import net.cyndeline.rlgraph.isomorphism.jGraphT.SubGraphIsomorphismInspector
import net.cyndeline.rldungeon.dgs.graph.{DGSFunctionality, Room}
import scalax.collection.GraphPredef.OuterEdge

/**
 * Specifies a single left-hand side of a graph grammar, and multiple right-hand side ComponentProductions.
 * Allows random selection with probabilities between each ComponentProduction.
 *
 * @param compProductions A list of all ComponentProductions that this multi production may apply, paired with
 *                        a value indicating the probability of that production to be selected. The probability
 *                        of an individual element is its value divided by the sum of all values.
 * @param random Used to select ComponentProductions randomly.
 */
class MultiProduction[VType <: Room[VType]: TypeTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]: ({type l[M[_]] = TypeTag[M[VType]]})#l : ({type l[M[_]] = ClassTag[M[VType]]})#l](
                                                              pattern: Graph[VType, EType],
                                                              matcher: ElementEquivalence[VType, EType],
                                                              negativeCondition: Option[NegativeCondition[VType, EType]],
                                                              compProductions: List[(ComponentProduction[VType, EType], Double)],
                                                              random: Random,
                                                              isomorphismMapper: IsomorphicMapping,
                                                              morphismFactory: MorphismFactory,
                                                              probabilitySelector: RandomCollection[ComponentProduction[VType, EType]]
                                                              ) extends Production[VType, EType](pattern, matcher, negativeCondition, random, isomorphismMapper, morphismFactory) {

  /**
   * Constructs a new production, taking only the user-supplied classes.
   */
  def this(pattern: Graph[VType, EType],
           matcher: ElementEquivalence[VType, EType],
           negativeCondition: Option[NegativeCondition[VType, EType]],
           compProductions: List[(ComponentProduction[VType, EType], Double)],
           random: Random) =
              this(pattern, matcher, negativeCondition, compProductions, random, new SubGraphIsomorphismInspector(), new MorphismFactory(), new ProbabilityCollection[ComponentProduction[VType, EType]](random))

  val productionProbabilitySelector = probabilitySelector
  compProductions.foreach(kv => productionProbabilitySelector.add(kv._2, kv._1))

  /**
   * Constructs a new multi production with default isomorphism and morphism factory, and the probability of each
   * ComponentProduction set to 1.0.
   *
   * @param compProductions A list of all ComponentProductions that this multi production may apply.
   * @param random Used to select ComponentProductions randomly.
   */
  def this(pattern: Graph[VType, EType],
           matcher: ElementEquivalence[VType, EType],
           negativeCondition: Option[NegativeCondition[VType, EType]],
           random: Random,
           compProductions: ComponentProduction[VType, EType]*) = this(pattern, matcher, negativeCondition, compProductions.map(p => (p, 1.0)).toList, random)

  /**
   * Modifies a graph.
   *
   * @param graph Graph to modify.
   * @return the graph resulting from modification if one was performed, otherwise None.
   */
  override def apply(graph: Graph[VType, EType]): Option[Graph[VType, EType]] = {
    val matchingVertices: Option[Morphism[VType]] = getMatch(graph)
    val compProduction = productionProbabilitySelector.next

    if (matchingVertices.isDefined) {
      Some(compProduction.apply(pattern, matchingVertices.get, graph))
    } else {
      None
    }
  }
}
